<?php
/**
 * DotHerbert
 *
 * @package    objects
 * @subpackage factory
 * @copyright  2009 extreme-programmers
 * @license    http://dotherbert.extreme-programmers.com/license   BSD License
 * @version    $Id:$
 */

namespace dotherbert\objects\factory\parser;
use dotherbert\objects\factory\config;

/**
 * XmlParser
 *
 * @package    objects
 * @subpackage factory
 * @author     Johannes Trink <johannes.trink@extreme-programmers.com>
 */
class XmlParser implements Parser
{
    private $basePath, $definition = array(), $properties = array(), $xmlPath;

    private $elementType = array('objects' => '');

    private $xmlDefinitions = array();

    /**
     * Constructor
     *
     * @param string $xmlPath
     */
    public function __construct($xmlPath)
    {
        $this->basePath = dirname($xmlPath);
        $this->xmlPath = $xmlPath;
    }

    /**
     * Get base path
     *
     * @return string
     */
    public function getBasePath()
    {
        return $this->basePath;
    }

    /**
     * Load imports
     *
     * @param string $path
     * @return void
     */
    private function loadImports($path)
    {
        $xml = simplexml_load_file($path);
        $this->xmlDefinitions[] = $xml;
        $xml->registerXPathNamespace('dh', 'http://dotherbert.extreme-programmers.com/schema/objects');
        $importsXml = $xml->xpath('/dh:objects/dh:import');
        foreach ($importsXml as $import)  {
            $importPath = $this->getBasePath() . DIRECTORY_SEPARATOR
                        . (string) $import->attributes()->resource;
            $this->loadImports($importPath);
        }

    }

    /**
     * Parse
     *
     * @return array
     */
    public function parse()
    {
        $this->loadImports($this->xmlPath);

        $objects = array();
        foreach ($this->xmlDefinitions as $xml) {
            foreach ($xml as $xmlObject) {
                if ('object' != $xmlObject->getName()) {
                    continue;
                }
                $attributes = $xmlObject->attributes();
                $object = new config\Object();
                foreach ($attributes as $key => $value)
                {
                    if ('factory-method' == $key) {
                        $method = 'setFactoryMethod';
                    } else {
                        $method = 'set' . ucfirst($key);
                    }
                    $object->$method((string) $value);
                }
                foreach ($xmlObject->children() as $child) {
                    $this->parseNode($object, $child);
                }
                $objects[$object->getId()] = $object;
            }
        }

        return $objects;
    }

    /**
     * Parse node
     *
     * @param object $element
     * @param SimpleXMLElement $node
     * @return void
     */
    private function parseNode($element, \SimpleXMLElement $node)
    {
        $attributes = $node->attributes();
        switch ($node->getName()) {
            case 'property':
                if (isset($attributes['ref'])) {
                    $property = new config\RefProperty();
                } else {
                    if (isset($node->ref)) {
                        $property = new config\RefProperty();
                        $refAttributes = $node->ref->attributes();
                        $property->setRef((string)$refAttributes->object);
                    } else {
                        $property = new config\ValueProperty();
                        if (isset($node->value)) {
                            $valueAttributes = $node->value->attributes();
                            $property->setType((string) $valueAttributes->type);
                            $property->setValue((string) $node->value);
                        }
                    }
                }
                $this->setAttributes($property, $attributes);
                $element->addProperty($property);
                break;
        }
    }

    /**
     * Set attributes
     *
     * @param object $element
     * @param SimpleXMLElement $attributes
     * @return void
     */
    private function setAttributes($element, \SimpleXMLElement $attributes)
    {
        foreach ($attributes as $key=>$value)
        {
//            echo $key . ' - ' . $value;
            if ('factory-method' == $key) {
                $method = 'setFactoryMethod';
            } else {
                $method = 'set' . ucfirst($key);
            }
            $element->$method((string) $value);
        }
    }

    /**
     * Parse
     *
     * @return array
     */
    public function parseOld()
    {
        $elements = simplexml_load_file($this->getXmlPath());

        $this->parseElements($elements, $this->definition);

        if (0 < count($this->properties)) {
            array_walk_recursive($this->definition, array($this, 'replaceProperties'));
        }

        return $this->definition;
    }

    public function replaceProperties(&$item, $key)
    {
        if (0 === strpos($item, '${')) {
            $item = trim($item, '${}');
            $item = strtr($item, $this->properties);
        }
    }

    /**
     * Parse elements
     *
     * @param object $elements
     * @param array $definition
     * @return void
     */
    private function parseElements(\SimpleXMLElement $elements, &$definition)
    {
        foreach ($elements as $element) {
            $this->parseElement($element, $definition);
        }
    }

    /**
     * Parse element
     *
     * @param \SimpleXMLElement $element
     * @param array             $definition
     * @return void
     */
    private function parseElement(\SimpleXMLElement $element, &$definition)
    {
        $name = $element->getName();

        $attributes = current((array) $element->attributes());

        switch ($name) {
            case 'import':
                $elements = simplexml_load_file($this->getConfigDir() . '/' . $attributes['resource']);
                $this->parseElements($elements, $this->definition);
                break;
            case 'property-placeholder':
                $propertyParser = new \dotherbert\core\config\PropertyParser($this->basePath . '/' . $attributes['location']);
                $config = $propertyParser->parse($attributes['section']);
                $this->properties = array_merge($this->properties, $config);
                break;
            case 'ref':
            case 'value':
                $definition[$name] = $attributes;
                if (0 == count($element->children())) {
                    $cdata = (string) $element;
                    if ('' !== $cdata) {
                        $definition[$name]['#cdata'] = $cdata;
                    }
                }
                break;
            default:
                if (isset($attributes['id'])) {
                    $key = $attributes['id'];
                    unset($attributes['id']);
                } elseif (isset($attributes['name'])) {
                    $key = $attributes['name'];
                    unset($attributes['name']);
                } else {
                    $definition[$name][] = null;
                    end($definition[$name]);
                    $key = key($definition[$name]);
                }
                if (isset($definition[$name][$key])) {
                    $definition[$name][$key] = array_merge($definition[$name][$key], $attributes);
                } else {
                    $definition[$name][$key] = $attributes;
                }
                foreach ($element as $child) {
                    $this->parseElement($child, $definition[$name][$key]);
                }
                break;
        }
    }

    /**
     * Get xml path
     *
     * @return string
     */
    private function getXmlPath()
    {
        return $this->xmlPath;
    }

    /**
     * Get config dir
     *
     * @return string
     */
    private function getConfigDir()
    {
        return $this->configDir;
    }
}